package com.febs.security.code.img;

import com.febs.common.domain.FebsConstant;
import com.febs.common.helper.StringRedisHelper;
import com.febs.security.code.ValidateCode;
import com.febs.security.exception.ValidateCodeException;
import com.febs.security.properties.FebsSecurityProperties;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;

/**
 * 短信验证码验证
 *
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2021/6/18
 */
@SuppressWarnings("NullableProblems")
public class ImageCodeFilter extends OncePerRequestFilter implements InitializingBean {

    private AuthenticationFailureHandler authenticationFailureHandler;

    private FebsSecurityProperties securityProperties;

    private boolean enableRedisCache;

    private StringRedisHelper redisHelper;

    private SessionStrategy sessionStrategy;

    private Set<String> url = CollUtil.newHashSet();

    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ",");
        url.addAll(Arrays.asList(configUrls));
        url.add(securityProperties.getCode().getImage().getLoginProcessingUrl());
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        boolean match = false;
        for (String u : url) {
            if (pathMatcher.match(u, request.getRequestURI())) {
                match = true;
            }
        }
        if (match) {
            try {
                validateCode(new ServletWebRequest(request));
            } catch (ValidateCodeException e) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        chain.doFilter(request, response);
    }

    private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
        String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), FebsConstant.WEB_NAME_IMAGE_CODE);
        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException("验证码不能为空！");
        }

        String key = servletWebRequest.getRequest().getSession().getId();
        String codeKey = FebsConstant.CACHE_KEY_TOKEN_PREFIX + key;
        ValidateCode codeInCache = null;

        if (enableRedisCache) {
            String codeInRedis = redisHelper.get(codeKey);
            if (codeInRedis != null) {
                codeInCache = Optional.of(JSONUtil.toBean(codeInRedis, ValidateCode.class)).orElse(null);
                redisHelper.delete(codeKey);
            }
        } else {
            Object codeInSession = sessionStrategy.getAttribute(servletWebRequest, codeKey);
            if (codeInSession != null) {
                codeInCache = JSONUtil.toBean(StrUtil.toString(codeInSession), ValidateCode.class);
                sessionStrategy.removeAttribute(servletWebRequest, FebsConstant.CACHE_KEY_TOKEN_PREFIX);
            }
        }

        if (codeInCache == null) {
            throw new ValidateCodeException("验证码不存在，请重新发送！");
        }
        if (codeInCache.isExpire()) {
            sessionStrategy.removeAttribute(servletWebRequest, FebsConstant.CACHE_KEY_TOKEN_PREFIX);
            throw new ValidateCodeException("验证码已过期，请重新发送！");
        }
        if (!StringUtils.equalsIgnoreCase(codeInCache.getCode(), codeInRequest)) {
            throw new ValidateCodeException("验证码不正确！");
        }

    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    public void setSecurityProperties(FebsSecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    public void setEnableRedisCache(boolean enableRedisCache) {
        this.enableRedisCache = enableRedisCache;
    }

    public void setRedisHelper(StringRedisHelper redisHelper) {
        this.redisHelper = redisHelper;
    }

    public void setSessionStrategy(SessionStrategy sessionStrategy) {
        this.sessionStrategy = sessionStrategy;
    }
}
